"""
位置数据处理器

处理IoT设备的位置数据，包括GPS定位、地理围栏判断、考勤打卡等功能
"""

import asyncio
import math
from datetime import datetime, timedelta
from typing import Dict, Any, Optional, List

import pytz

from .base_handler import BaseHandler
from client import es_client
from dao.es_dao.card_dao import update_card_location
from dao.es_dao.p_clock_record_dao import (
    mapping_full_data_new, add_clock_record,
    mapping_clock_record_new, get_clock_record
)
from dao.mongo_dao.location import save_location_mongo
from model.es.card import ElectronicCardModel, ClockRule, ClockRuleTime
from model.wristband_models import LocationData
from service.battery import get_intensity_redis
from utils.date_utils import DateTimeCombiner, date_time_to_str_000z
from utils.geo_utils import is_point_in_geo_fence, calculate_distance


class LocationHandler(BaseHandler):
    """位置数据处理器"""
    
    def __init__(self):
        super().__init__()
        self.beijing_tz = pytz.timezone('Asia/Shanghai')
    
    async def handle(self, device_id: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """
        处理位置数据
        
        Args:
            device_id: 设备ID
            data: 位置数据字典
            
        Returns:
            处理结果，包含设备卡片信息
        """
        try:
            # 将字典数据转换为LocationData模型
            location = LocationData(**data)
            return await self._process_location_data(location)
            
        except Exception as e:
            self.log_error(device_id, "位置", e)
            return None
    
    async def _process_location_data(self, location: LocationData) -> Optional[ElectronicCardModel]:
        """
        处理位置数据的核心逻辑
        
        业务流程：
        1. 获取设备卡片信息
        2. 并发执行：更新在线状态、更新位置、判断围栏状态
        3. 计算与围栏的距离（如果不在围栏内）
        4. 并发执行：更新围栏状态、保存MongoDB数据、处理考勤打卡
        5. 更新Redis状态
        
        Args:
            location: 位置数据模型
            
        Returns:
            设备卡片信息
        """
        # 步骤1: 获取设备卡片信息（必须先执行）
        card = await self.get_device_card(location.device_id)
        if not card:
            self.logger.warning(f"未找到设备卡片信息: {location.device_id}")
            return None
        
        # 步骤2: 并发执行独立操作
        online_task = self.update_device_online_status(location.timestamp, card.es_id)
        location_task = self._update_card_location(location, card.es_id)
        fence_task = self._check_geo_fence(location, card, refresh=True)
        
        _, _, in_fence = await asyncio.gather(online_task, location_task, fence_task)
        
        # 步骤3: 计算距离（只在不在围栏时执行）
        distance = 0
        if not in_fence and card.fence:
            distance = await self._calculate_fence_distance(location, card.fence[0].points)
        
        # 步骤4: 并发执行最终操作
        fence_update_task = self._update_fence_status(location, in_fence)
        mongo_save_task = self._save_to_mongo(location, in_fence, distance)
        clock_save_task = self._process_attendance(card, location, in_fence)
        
        await asyncio.gather(fence_update_task, mongo_save_task)
        clock_state = await clock_save_task
        
        # 步骤5: 更新Redis考勤状态
        await self.redis_hset_async('clock_state', location.device_id, clock_state)
        
        return card
    
    async def _update_card_location(self, location: LocationData, card_es_id: str) -> None:
        """更新卡片位置信息"""
        try:
            await update_card_location(location, card_es_id)
        except Exception as e:
            self.logger.error(f"更新卡片位置失败: {location.device_id}, 错误: {e}")
    
    async def _check_geo_fence(self, location: LocationData, card: ElectronicCardModel, 
                             refresh: bool = False) -> bool:
        """
        检查设备是否在地理围栏内
        
        Args:
            location: 位置数据
            card: 设备卡片信息
            refresh: 是否强制刷新，不使用缓存
            
        Returns:
            是否在围栏内
        """
        try:
            if refresh:
                # 强制重新计算围栏状态
                if not card.fence:
                    return False
                return is_point_in_geo_fence(location.longitude, location.latitude, 
                                           card.fence[0].points)
            else:
                # 先从Redis缓存获取
                fence_status = await self.redis_hget_async("in_fence", location.device_id)
                if fence_status is None:
                    # 缓存不存在，重新计算
                    if not card.fence:
                        return False
                    return is_point_in_geo_fence(location.longitude, location.latitude, 
                                               card.fence[0].points)
                else:
                    # 从缓存返回结果
                    return fence_status == b'1'
                    
        except Exception as e:
            self.logger.error(f"检查地理围栏失败: {location.device_id}, 错误: {e}")
            return False
    
    async def _calculate_fence_distance(self, location: LocationData, fence_points: List) -> float:
        """
        计算设备与围栏的距离
        
        Args:
            location: 位置数据
            fence_points: 围栏点列表
            
        Returns:
            距离（米）
        """
        try:
            return await calculate_distance(location, fence_points)
        except Exception:
            return 0.0
    
    async def _update_fence_status(self, location: LocationData, in_fence: bool) -> None:
        """
        更新围栏状态到Redis
        
        Args:
            location: 位置数据
            in_fence: 是否在围栏内
        """
        try:
            # 更新围栏状态
            fence_status = 1 if in_fence else 0
            await self.redis_hset_async("in_fence", location.device_id, fence_status)
            
            # 更新最后在围栏内的时间戳（同步操作，因为原代码如此）
            redis_client = self.get_redis_client()
            redis_client.hset("last_in_fence_timestamp", location.device_id, location.timestamp)
            
        except Exception as e:
            self.logger.error(f"更新围栏状态失败: {location.device_id}, 错误: {e}")
    
    async def _save_to_mongo(self, location: LocationData, in_fence: bool, distance: float) -> None:
        """保存位置数据到MongoDB"""
        try:
            await save_location_mongo(in_fence, location, distance)
        except Exception as e:
            self.logger.error(f"保存MongoDB数据失败: {location.device_id}, 错误: {e}")
    
    async def _process_attendance(self, card: ElectronicCardModel, location: LocationData, 
                                in_fence: bool) -> int:
        """
        处理考勤打卡逻辑
        
        Args:
            card: 设备卡片信息
            location: 位置数据
            in_fence: 是否在围栏内
            
        Returns:
            考勤状态: 0=未打卡, 1=未打卡完, 2=打卡完
        """
        try:
            punch_time = datetime.fromtimestamp(location.timestamp)
            clock_rules = card.clock_rule
            
            # 判断是否需要获取前一天的考勤记录（处理跨天打卡）
            is_early = await self._is_early_than_next_day_rule(punch_time, clock_rules)
            delta_day = 1 if is_early else 0
            
            # 获取最新的考勤记录和状态
            clock_record, clock_state, start_of_day = await self._get_latest_attendance_record(
                card.imei, clock_rules, punch_time, delta_day
            )
            
            # 获取设备活动强度
            intensity = await get_intensity_redis(card.imei)
            
            return await self._handle_attendance_by_state(
                card, clock_record, clock_state, clock_rules, punch_time, 
                location, in_fence, intensity, start_of_day
            )
            
        except Exception as e:
            self.logger.error(f"处理考勤逻辑失败: {location.device_id}, 错误: {e}")
            return 0
    
    async def _is_early_than_next_day_rule(self, punch_time: datetime, 
                                         clock_rules: List[ClockRule]) -> bool:
        """
        判断打卡时间是否早于跨天规则的下班时间
        
        Args:
            punch_time: 打卡时间
            clock_rules: 考勤规则列表
            
        Returns:
            是否早于跨天规则
        """
        try:
            if not clock_rules:
                return False
                
            # 获取最后一个规则的下班时间
            last_rule_time = clock_rules[-1].rule_time[-1]
            if not last_rule_time.next_day:
                return False
            
            # 构建基准时间
            base_time = datetime.strptime(last_rule_time.clock_in_time, '%H:%M').replace(
                year=punch_time.year, month=punch_time.month, day=punch_time.day
            )
            
            # 计算阈值时间
            threshold_time = base_time + timedelta(milliseconds=last_rule_time.after_time)
            
            return threshold_time > punch_time
            
        except Exception:
            return False
    
    async def _get_latest_attendance_record(self, imei: str, clock_rules: List[ClockRule], 
                                          current_time: datetime, delta_day: int = 0):
        """
        获取最新的考勤记录
        
        Args:
            imei: 设备IMEI
            clock_rules: 考勤规则
            current_time: 当前时间
            delta_day: 天数偏移
            
        Returns:
            (考勤记录, 考勤状态, 当天开始时间)
        """
        # 计算查询时间范围
        start_of_day = current_time.replace(
            hour=0, minute=0, second=0, microsecond=0
        ) - timedelta(days=delta_day)
        
        end_of_day = current_time.replace(
            hour=23, minute=59, second=59, microsecond=999999
        )
        
        # 从ES获取考勤记录
        clock_record = await get_clock_record(start_of_day, end_of_day, [imei])
        
        # 分析考勤状态
        clock_state = 0  # 默认未打卡
        
        if clock_record:
            record_date = DateTimeCombiner.parse_iso_to_date(clock_record[0]['source']['dt'])
            is_same_date = (record_date.date() == current_time.date())
            
            record_count = len(clock_record[0]['source']['clockRecord'])
            expected_count = len(clock_rules[0].rule_time) if clock_rules else 0
            
            if record_count == expected_count:
                # 考勤记录完整
                clock_state = 2 if is_same_date else 0
            elif record_count < expected_count:
                # 考勤记录不完整
                clock_state = 1
        
        return clock_record, clock_state, start_of_day
    
    async def _handle_attendance_by_state(self, card: ElectronicCardModel, clock_record: List,
                                        clock_state: int, clock_rules: List[ClockRule],
                                        punch_time: datetime, location: LocationData,
                                        in_fence: bool, intensity: Dict, start_of_day: datetime) -> int:
        """
        根据考勤状态处理打卡逻辑
        
        Args:
            card: 设备卡片信息
            clock_record: 考勤记录
            clock_state: 考勤状态
            clock_rules: 考勤规则
            punch_time: 打卡时间
            location: 位置数据
            in_fence: 是否在围栏内
            intensity: 活动强度数据
            start_of_day: 当天开始时间
            
        Returns:
            新的考勤状态
        """
        if clock_state == 0:
            # 当天未打卡，创建新的考勤记录
            return await self._handle_first_attendance(
                card, clock_rules, punch_time, location, in_fence, intensity, start_of_day.date()
            )
        elif clock_state == 1:
            # 当天未打卡完，补充考勤记录
            return await self._handle_incomplete_attendance(
                card, clock_record, clock_rules, punch_time, location, in_fence
            )
        else:
            # 当天已打卡完
            return 2
    
    async def _handle_first_attendance(self, card: ElectronicCardModel, clock_rules: List[ClockRule],
                                     punch_time: datetime, location: LocationData, in_fence: bool,
                                     intensity: Dict, record_date) -> int:
        """处理首次打卡"""
        if intensity.get('intensity', 0) <= 0:
            return 0
        
        # 构建考勤记录
        new_records, new_state = await self._build_attendance_record(
            card, [], clock_rules, punch_time, location, in_fence, 0, record_date
        )
        
        if not new_records:
            return 0
        
        # 保存到ES
        dt_result = await self._get_attendance_datetime(new_records)
        full_record = await mapping_full_data_new(dt_result, new_records, card)
        
        es = await self.get_es_client()
        await es.index_document('p_clock_record', full_record)
        
        return 1 if len(new_records) == 1 else 2
    
    async def _handle_incomplete_attendance(self, card: ElectronicCardModel, clock_record: List,
                                          clock_rules: List[ClockRule], punch_time: datetime,
                                          location: LocationData, in_fence: bool) -> int:
        """处理未完成的考勤打卡"""
        if not clock_record:
            return 1
        
        record_date = DateTimeCombiner.parse_iso_to_date(clock_record[0]['source']['dt'])
        existing_records = clock_record[0]['source']['clockRecord']
        
        expected_count = len(clock_rules[0].rule_time) if clock_rules else 0
        
        if len(existing_records) >= expected_count:
            return 2
        
        # 计算当前应该处理的考勤规则索引
        rule_index = math.floor(len(existing_records) / 2)
        
        # 构建新的考勤记录
        new_records, _ = await self._build_attendance_record(
            card, existing_records, clock_rules, punch_time, location, 
            in_fence, rule_index, record_date.date()
        )
        
        if new_records:
            # 将新记录添加到现有记录中
            for new_record in new_records:
                await add_clock_record(new_record, clock_record[0]['_id'])
            return 2
        else:
            return 1
    
    async def _build_attendance_record(self, card: ElectronicCardModel, existing_records: List,
                                     clock_rules: List[ClockRule], punch_time: datetime,
                                     location: LocationData, in_fence: bool, rule_index: int,
                                     record_date) -> tuple:
        """
        构建考勤记录
        
        Returns:
            (新记录列表, 考勤状态)
        """
        if not clock_rules or not clock_rules[0].rule_time:
            return [], 0
        
        rule_times = clock_rules[0].rule_time
        current_timestamp = punch_time.timestamp()
        
        # 获取当前规则时间段
        if rule_index * 2 >= len(rule_times):
            return [], 0
        
        start_rule = rule_times[rule_index * 2]
        end_rule = rule_times[rule_index * 2 + 1] if (rule_index * 2 + 1) < len(rule_times) else None
        
        # 计算时间范围
        start_early, start_base, start_after = await self._get_rule_time_range(record_date, start_rule)
        
        new_records = []
        
        # 判断是上班打卡还是下班打卡
        if len(existing_records) % 2 == 0:
            # 上班打卡
            if in_fence and start_early < current_timestamp < start_after:
                status = '正常' if current_timestamp <= start_base else '迟到'
                new_records.append(await mapping_clock_record_new(location, start_rule, status))
                return new_records, 1
            elif in_fence and end_rule:
                # 处理缺卡情况
                end_early, end_base, end_after = await self._get_rule_time_range(record_date, end_rule)
                if current_timestamp >= end_base - 3600:  # 下班前一小时内算缺卡
                    new_records.append(await mapping_clock_record_new(location, start_rule, '缺卡'))
                    new_records.append(await mapping_clock_record_new(location, end_rule, '缺卡'))
                    return new_records, 2
        else:
            # 下班打卡
            if end_rule:
                end_early, end_base, end_after = await self._get_rule_time_range(record_date, end_rule)
                if end_early < current_timestamp < end_after:
                    status = '正常' if in_fence else '早退'
                    new_records.append(await mapping_clock_record_new(location, end_rule, status))
                    return new_records, 2
                elif current_timestamp > end_after + 900:  # 超过下班15分钟
                    # 使用最后在围栏内的时间
                    status = '正常' if in_fence else '早退'
                    last_timestamp = await self.redis_hget_async("last_in_fence_timestamp", card.imei)
                    if last_timestamp:
                        location.timestamp = int(last_timestamp)
                    new_records.append(await mapping_clock_record_new(location, end_rule, status))
                    return new_records, 2
        
        return [], 1 if len(existing_records) % 2 == 0 else 1
    
    async def _get_rule_time_range(self, record_date, rule: ClockRuleTime) -> tuple:
        """
        获取考勤规则的时间范围
        
        Args:
            record_date: 记录日期
            rule: 考勤规则时间
            
        Returns:
            (早到时间戳, 基准时间戳, 晚到时间戳)
        """
        clock_time = datetime.strptime(rule.clock_in_time, '%H:%M').time()
        base_timestamp = datetime.combine(record_date, clock_time).timestamp()
        
        if rule.next_day:
            base_timestamp += 86400  # 加一天
        
        early_timestamp = base_timestamp - rule.before_time / 1000
        late_timestamp = base_timestamp + rule.after_time / 1000
        
        return early_timestamp, base_timestamp, late_timestamp
    
    async def _get_attendance_datetime(self, records: List) -> str:
        """获取考勤记录的时间字符串"""
        if not records:
            return ""
        
        punch_time = records[0]['punchInTime']
        rule_time = records[0]['ruleTime']
        
        return await self._replace_datetime(punch_time, rule_time)
    
    async def _replace_datetime(self, punch_time: str, rule_time: str) -> str:
        """
        替换打卡时间为规则时间
        
        Args:
            punch_time: 打卡时间
            rule_time: 规则时间
            
        Returns:
            格式化的时间字符串
        """
        try:
            # 解析规则时间
            time_obj = datetime.strptime(rule_time, '%H:%M:%S').time()
            
            # 解析打卡时间并替换时间部分
            punch_date = DateTimeCombiner.parse_iso_to_date(punch_time)
            new_datetime = punch_date.replace(
                hour=time_obj.hour,
                minute=time_obj.minute,
                second=time_obj.second,
                microsecond=0
            )
            
            return date_time_to_str_000z(new_datetime)
            
        except Exception as e:
            self.logger.error(f"时间替换失败: {e}")
            return punch_time